<!DOCTYPE html>
<!--
  ~ Copyright 2016-present Open Networking Laboratory
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!--
  Testing transformations for transitioning between overhead and
  perspective projections of two layers.
  -->
<html>
<head>
    <meta charset="utf-8">
    <title>Layer Transformations</title>

    <script src="../tp/d3.js"></script>
    <script src="../tp/jquery-2.1.1.min.js"></script>

    <style>
        html,
        body {
            background-color: #ccc;
            font-family: Arial, Helvetica, sans-serif;
            font-size: 9pt;
        }

        svg {
            position: absolute;
            background-color: #fff;
            top: 30px;
            left: 60px;
        }

        svg text {
            font-size: 3pt;
        }

    </style>
</head>
<body>
    <svg width="1000px" height="600px" viewBox="0 0 160 120"></svg>

    <script>
        (function (){

            // Configuration...
            var w = 160,
                h = 120,
                time = 1500;

            var pktData = [
                    [20,60,'a'],
                    [60,20,'b'],
                    [100,20,'c'],
                    [140,60,'d'],
                    [100,100,'e'],
                    [60,100,'f'],
                    [20,20,'w'],
                    [140,20,'x'],
                    [20,100,'y'],
                    [140,100,'z']
                ],
                optData = [
                    [40,40,'p'],
                    [120,40,'q'],
                    [120,80,'r'],
                    [40,80,'s'],
                    [20,20,'j'],
                    [140,20,'k'],
                    [20,100,'l'],
                    [140,100,'m']
                ],
                linkData = [
                    ['a','p'],
                    ['p','b'],
                    ['b','c'],
                    ['c','q'],
                    ['q','d'],
                    ['d','r'],
                    ['r','e'],
                    ['e','f'],
                    ['f','s'],
                    ['s','a'],
                    ['s','q'],
                    ['p','r'],
                    ['b','f'],
                    ['c','e'],
                    ['w','j'],
                    ['x','k'],
                    ['z','m'],
                    ['y','l']
                ];

            // Transform parameters
            var tf = {
                    tt:  -.7,     // x skew y factor
                    xsk: -35,     // x skew angle
                    ysc: 0.5,     // y scale
                    ytr: 50,      // y translate
                    pad: 5
                },
                rectFill = {
                    pkt: 'rgba(130,130,170,0.3)',
                    opt: 'rgba(170,130,170,0.3)'
                };

            // Internal state...
            var nodes = [],
                links = [],
                overhead = true,
                xffn;

            // D3/DOM magic...
            var svg = d3.select('svg'),
                nodeG,
                linkG,
                node,
                link,
                force,
                pktLayer,
                optLayer;


            // General functions ...
            function isF(f) {
                return $.isFunction(f) ? f : null;
            }

            function translate(x,y) {
                return 'translate(' + x + ',' + y + ')';
            }

            function scale(x,y) {
                return 'scale(' + x + ',' + y + ')';
            }
            function skewX(x) {
                return 'skewX(' + x + ')';
            }


            // Key Bindings...
            var keyHandler = {
                T: transform
            };

            function whatKey(code) {
                switch (code) {
                    case 13: return 'enter';
                    case 16: return 'shift';
                    case 17: return 'ctrl';
                    case 18: return 'alt';
                    case 27: return 'esc';
                    case 32: return 'space';
                    case 37: return 'leftArrow';
                    case 38: return 'upArrow';
                    case 39: return 'rightArrow';
                    case 40: return 'downArrow';
                    case 91: return 'cmdLeft';
                    case 93: return 'cmdRight';
                    case 187: return 'equals';
                    case 189: return 'dash';
                    case 191: return 'slash';
                    default:
                        if ((code >= 48 && code <= 57) ||
                                (code >= 65 && code <= 90)) {
                            return String.fromCharCode(code);
                        } else if (code >= 112 && code <= 123) {
                            return 'F' + (code - 111);
                        }
                        return '.';
                }
            }

            function keyIn() {
                var event = d3.event,
                        keyCode = event.keyCode,
                        key = whatKey(keyCode),
                        fn = isF(keyHandler[key]);
                if (fn) {
                    fn(key, keyCode, event);
                }
            }

            // Key events....
            function transform() {
                overhead = !overhead;
                if (overhead) {
                    toOverhead();
                } else {
                    toOblique();
                }
            }

            function toOverhead() {
                xffn = null;
                hidePlane(pktLayer);
                hidePlane(optLayer);
                transitionNodes();
            }

            function padBox(box, p) {
                box.x -= p;
                box.y -= p;
                box.width += p*2;
                box.height += p*2;
            }

            function toOblique() {
                var box = nodeG.node().getBBox();
                padBox(box, tf.pad);

                xffn = function (xy, dir) {
                    var x = xy.x + xy.y*tf.tt,
                        y = xy.y*tf.ysc + tf.ysc*tf.ytr*dir;
                    return { x: x, y: y};
                };

                showPlane(pktLayer, box, -1);
                showPlane(optLayer, box, 1);
                transitionNodes();
            }

            function transitionNodes() {
            // note: turn off force layout while transitioning.. if it is on
//                force.stop();

                if (xffn) {
                    nodes.forEach(function (d) {
                        var dir = d.type === 'pkt' ? -1 : 1,
                            oldxy = {x: d.x, y: d.y},
                            coords = xffn(oldxy, dir);
                        d.oldxy = oldxy;
                        d.x = coords.x;
                        d.y = coords.y;
                    });
                } else {
                    nodes.forEach(function (d) {
                        d.x = d.oldxy.x;
                        d.y = d.oldxy.y;
                        delete d.oldxy;
                    });
                }

                nodeG.selectAll('.node')
                        .transition()
                        .duration(time)
                        .attr({
                            transform: function (d) {
                                return translate(d.x, d.y);
                            }
                        });

                linkG.selectAll('.link')
                        .transition()
                        .duration(time)
                        .attr({
                            x1: function (d) { return d.source.x; },
                            y1: function (d) { return d.source.y; },
                            x2: function (d) { return d.target.x; },
                            y2: function (d) { return d.target.y; }
                        });
            }

            function showPlane(layer, box, dir) {
                layer.select('rect')
                    .attr(box)
                    .attr('opacity', 0)
                    .transition()
                        .duration(time)
                        .attr('opacity', 1)
                        .attr('transform', obliqueXform(dir));
            }

            function hidePlane(layer) {
                var rect = layer.select('rect');
                rect.transition()
                        .duration(time)
                        .attr('opacity', 0)
                        .attr('transform', overheadXform());

            }

            function obliqueXform(dir) {
                return scale(1, tf.ysc) + translate(0, dir * tf.ytr) + skewX(tf.xsk);
            }


            function overheadXform() {
                return skewX(0) + translate(0,0) + scale(1,1);
            }

            // Nodes and Links...
            function prepareNodes() {
                var hw = w/2,
                    hh = h/2;

                function addNode(t, d) {
                    nodes.push({
                        type: t,
                        x: d[0] - hw,
                        y: d[1] - hh,
                        id: d[2],
                        fixed: true
                    });
                }

                optData.forEach(function (d) {
                    addNode('opt', d);
                });
                pktData.forEach(function (d) {
                    addNode('pkt', d);
                });
            }

            function findNode(id) {
                for (var i=0,n=nodes.length; i<n; i++) {
                    if (nodes[i].id === id) {
                        return nodes[i];
                    }
                }
                return null;
            }

            function prepareLinks() {
                linkData.forEach(function (d) {
                    var src = d[0],
                        dst = d[1];
                    links.push({
                        id: src + '-' + dst,
                        source: findNode(src),
                        target: findNode(dst)
                    });
                });

            }

            function updateNodes() {
                node = nodeG.selectAll('.node')
                        .data(nodes, function (d) { return d.id; });

                var entering = node.enter()
                        .append('g').attr({
                            id: function (d) { return d.id; },
                            'class': function (d) { return 'node ' + d.type; }
                        });

                entering.each(function (d) {
                    var el = d3.select(this);
                    d.el = el;

                    el.append('rect').attr({
                        width: 5,
                        height: 5,
                        fill: function (d) {
                            return d.type === 'pkt' ? '#669' : '#969';
                        },
                        rx: 1,
                        transform: 'translate(-2.5,-2.5)'
                    });
                    el.append('text')
                            .text(d.id)
                            .attr({
                                dy: '0.9em',
                                'text-anchor': 'middle',
                                transform: 'translate(0,-2.5)',
                                fill: 'white'
                            });
                });
            }

            function updateLinks() {
                link = linkG.selectAll('.link')
                        .data(links, function (d) { return d.id; });

                var entering = link.enter()
                        .append('line').attr({
                            id: function (d) { return d.id; },
                            class: 'link',
                            stroke: '#888',
                            'stroke-width': 0.4,
                            opacity: 0.7
                        });

                entering.each(function (d) {
                    d.el = d3.select(this);

                });
            }

            function update() {
                updateNodes();
                updateLinks();
            }

            var ntick = 0;
            function tick() {
                console.log('tick ' + (++ntick));
                node.attr({
                    transform: function (d) { return translate(d.x, d.y); }
                });

                link.attr({
                    x1: function (d) { return d.source.x; },
                    y1: function (d) { return d.source.y; },
                    x2: function (d) { return d.target.x; },
                    y2: function (d) { return d.target.y; }
                });
            }

            function setOrigin(/*varargs*/) {
                var i, n, g;
                for (i= 0,n=arguments.length; i< n; i++) {
                     g = arguments[i];
                    g.attr('transform', translate(w/2, h/2));
                }
            }

            function initLayers() {
                optLayer.attr('class', 'layer').append('rect')
                        .attr('fill', rectFill.opt);
                pktLayer.attr('class', 'layer').append('rect')
                        .attr('fill', rectFill.pkt);
            }

            function init() {
                svg.append('text')
                        .text('Press the "T" key....')
                        .attr({ dy: '1.2em', fill: '#999'})
                        .style('font-size', '2.4pt')
                        .style('font-style', 'italic');

                optLayer = svg.append('g').attr('id', 'optLayer');
                pktLayer = svg.append('g').attr('id', 'pktLayer');
                linkG = svg.append('g').attr('id', 'links');
                nodeG = svg.append('g').attr('id', 'nodes');

                setOrigin(optLayer, pktLayer, linkG, nodeG);

                node = nodeG.selectAll('.node');
                link = linkG.selectAll('.link');

                initLayers();
                prepareNodes();
                prepareLinks();

                force = d3.layout.force()
                        .size([w,h])
                        .nodes(nodes)
                        .links(links)
                        .gravity(0.4)
                        .friction(0.7)
                        .on('tick', tick);
                update();
                tick();
                d3.select('body').on('keydown', keyIn);
            }

            init();
        })();
    </script>
</body>
</html>
